using System;
using System.Collections.Generic;

using Atomic.Libraries;
using Atomic.Structures;

namespace Atomic.Clusters
{
	[Serializable]
	public class ClusterExpansion
	{
		private List<ClusterClass> clusters;

		public ClusterExpansion(Structure lattice, IEnumerable<ClusterExpansionTerm> terms)
		{
			clusters = new List<ClusterClass>();
			foreach (ClusterExpansionTerm term in terms)
			{
				if (lattice == null)
				{
					lattice = term.Cluster.Lattice;
				}

				if (term.Cluster.Lattice != lattice)
				{
					throw new ArgumentException("Cluster defined on a different lattice.");
				}

				clusters.Add(term.Cluster);
			}

			Lattice = lattice;
			Terms = new ReadOnlyList<ClusterExpansionTerm>(terms);
		}

		protected static IEnumerable<double> TransformEnergies(IEnumerable<Structure> structures, IDictionary<Structure, double> energies)
		{
			// Ignore unused energies in the dictionary.
			foreach (Structure structure in structures)
			{
				yield return energies[structure];
			}
		}

		public double ExpandEnergy(Structure structure)
		{
			return ExpandEnergy(structure, new ClusterFunctionEvaluator(Lattice));
		}

		public virtual double ExpandEnergy(Structure structure, IClusterFunctionEvaluator evaluator)
		{
			double e = 0.0;

			IClusterFunctionValue value = evaluator.Evaluate(structure, clusters);
			int i = 0;
			foreach (double c in value.Correlations)
			{
				ClusterExpansionTerm term = Terms[i++];
				e += c * value.Scaling * term.Coefficient * term.Cluster.Multiplicity;
			}

			return e;
		}

		/// <summary>
		/// The lattice on which the clusters are defined.
		/// </summary>
		public Structure Lattice
		{
			get;
			private set;
		}

		public ReadOnlyList<ClusterExpansionTerm> Terms
		{
			get;
			private set;
		}
	}
}
